/* Create a layout for arbitrary graph using force-based algorithm */

#include <cmath>
#include <cstdlib>
#include <limits>

#include <memchart/types.hpp>

#include "chart_box.hpp"
#include "vector2.hpp"

#include "memory_chart.hpp"

namespace memchart
{
	using std::numeric_limits;

	/* Two node equilibrium distance */
	static const double l0 = 100;
	/* Elasticity constant */
	static const double l = 0.1;
	/* Coulomb force constant */
	static const double k = l0 * l0 * l0 * l;

	static const double damping = 0.9;
	static const double treshold = 10;
	static const double timestep = 0.1;

	static inline vector2 realpos (const chart_box & box)
	{
		return vector2(box.position.x - box.widget->get_width() / 2.0,
			box.position.y - box.widget->get_height() / 2.0);
	}

	void MemoryChart::layout_generic ()
	{
		/* Set velocity to 0 and position to random values */
		for (box_iter v = boxes.begin(); v != boxes.end(); ++v)
		{
			v->second.velocity = vector2(0, 0);
			v->second.position = vector2(std::rand(), std::rand());
		}

		/* Start simulation */
		double kinetic_energy;
		do
		{
			kinetic_energy = 0;

			for (box_iter v = boxes.begin(); v != boxes.end(); ++v)
			{
				chart_box & box = v->second;

				/* Add charge repulsion */
				for (box_iter v2 = boxes.begin(); v2 != boxes.end(); ++v2)
				{
					if (v2 != v)
					{
						vector2 r = box.position - v2->second.position;
						double rep = k / r.length2();
						box.force += r.normalize() * rep;
					}
				}

				/* Add spring attraction */
				for (edge_iter e = box.edges.begin(); e != box.edges.end(); ++e)
				{
					box_iter v2 = boxes.find(*e);
					if (v2 != boxes.end())
					{
						vector2 r = box.position - v2->second.position;
						double attr = - l * r.length();
						r.normalize() *= attr;
						box.force += r;
						v2->second.force -= r;
					}
				}
			}

			for (box_iter v = boxes.begin(); v != boxes.end(); ++v)
			{
				chart_box & box = v->second;
				box.velocity = (box.velocity + box.force * timestep) * damping;
				box.position += box.velocity * timestep;
				kinetic_energy += box.velocity.length2();
				box.force = vector2();
			}
		}
		while (kinetic_energy > treshold);

		/* Move the diagram to (0, 0) */
		vector2 topleft(numeric_limits<double>::max(),
			numeric_limits<double>::max());
		for (box_iter v = boxes.begin(); v != boxes.end(); ++v)
		{
			vector2 pos = realpos(v->second);
			if (pos.x < topleft.x)
				topleft.x = pos.x;
			if (pos.y < topleft.y)
				topleft.y = pos.y;
		}
		for (box_iter v = boxes.begin(); v != boxes.end(); ++v)
			v->second.position -= topleft;

		/* Position nodes */
		for (box_iter v = boxes.begin(); v != boxes.end(); ++v)
		{
			chart_box & box = v->second;
			move(*box.widget, realpos(box).x + 0.5, realpos(box).y + 0.5);
		}

		/* Calculate diagram size */
		vector2 bottomright(numeric_limits<double>::min(),
			numeric_limits<double>::min());
		for (box_iter v = boxes.begin(); v != boxes.end(); ++v)
		{
			const chart_box & box = v->second;
			vector2 pos = realpos(box);
			pos += vector2(box.widget->get_width(), box.widget->get_height());
			if (pos.x > bottomright.x)
				bottomright.x = pos.x;
			if (pos.y > bottomright.y)
				bottomright.y = pos.y;
		}
		set_size_request(bottomright.x + 1, bottomright.y + 1);
	}
}
